﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace Glue.Internals
{
    internal partial class ObjectMemberFactory
    {
        private class EnumerableMember : CollectionMemberBase
        {
            public EnumerableMember(IObjectMemberAccessor accessor):base(accessor)
            {
                if (!MemberType.IsOfInterfaceType(typeof(IEnumerable<>)))
                    throw new GlueException("Enumerable member has to be a generic Type. Is: " + MemberType.Name);
            }

            protected override object InvokeSpecialGet(object toInvokeOn)
            {
                var enumerable = InvokeGetOnActualObject(toInvokeOn);
                if (enumerable == null)
                    return null;
                return new GetterEnumerableCollection((IEnumerable)enumerable, this);
            }

            protected override void InvokeSpecialSet(object toInvokeOn, object toSet)
            {
                if (toSet == null) return;
                var list = ((ISetterCollection) toSet).GetList();
                InvokeSetOnActualObject(toInvokeOn, list);
            }

            public override IGlueCollection CreateCollection(IGlueCollection collection)
            {
                if (MemberType.IsInterface || MemberType.IsOfInterfaceType(typeof(IList)))
                {
                    var list = GenerateList();
                    return new SetterEnumerableCollection(list, value => list.Add(value), this);
                }
                if (MemberType.IsOfInterfaceType(typeof(ICollection<>)))
                {
                    var genericCollection = GenerateCollection();
                    return new SetterEnumerableCollection(
                        genericCollection, 
                        value => genericCollection.GetType().GetMethod("Add").Invoke(genericCollection, new []{value}), 
                        this);
                }

                throw new GlueException("This is not a writeable Collection: " + MemberType.GetReadableName());
            }

            private object GenerateCollection()
            {
                if (MemberType.IsInterface)
                {
                    Type destListType = typeof(Collection<>).MakeGenericType(GetElementType());
                    return destListType.CreateInstance();
                }
                return MemberType.CreateInstance();
            }

            private IList GenerateList()
            {
                if (MemberType.IsInterface)
                {
                    Type destListType = typeof(List<>).MakeGenericType(GetElementType());
                    return (IList)destListType.CreateInstance();
                }
                return (IList) MemberType.CreateInstance();
            }

            public override Type GetElementType()
            {
                return MemberType.GetGenericArguments()[0];
            }


            private class GetterEnumerableCollection:IGlueCollection
            {
                private readonly EnumerableMember creator;
                private readonly IEnumerator enumerator;

                public GetterEnumerableCollection(IEnumerable enumerable, EnumerableMember creator)
                {
                    this.creator = creator;
                    var list = new List<object>();
                    foreach (var o in enumerable)
                    {
                        list.Add(o);
                    }
                    Count = list.Count;
                    enumerator = list.GetEnumerator();
                }

                public bool MoveNext() { return enumerator.MoveNext(); }
                public object Current { get { return enumerator.Current; } }
                public int Count{get ; private set;}

                public bool CreatedBy(ICollectionMember collectionMember)
                {
                    return collectionMember == creator;
                }

                public void Add(object value) { throw new GlueException("System error: A GetterEnumerableCollection is trying to do Setter-stuff"); }
            }

            protected interface ISetterCollection : IGlueCollection
            {
                object GetList();
            }

            private class SetterEnumerableCollection : ISetterCollection
            {
                private readonly object list;
                private readonly Action<object> addToList;
                private readonly EnumerableMember creator;

                public SetterEnumerableCollection(object list, Action<object> addToList, EnumerableMember creator)
                {
                    this.list = list;
                    this.addToList = addToList;
                    this.creator = creator;
                }

                public bool MoveNext() { throw new GlueException("System error: A SetterEnumerableCollection is trying to do Getter-stuff"); }
                public object Current { get { throw new GlueException("System error: A SetterEnumerableCollection is trying to do Getter-stuff"); }  }
                public int Count { get { throw new GlueException("System error: A SetterEnumerableCollection is trying to do Getter-stuff"); } }

                public void Add(object value)
                {
                    addToList(value);
                }
                public bool CreatedBy(ICollectionMember collectionMember)
                {
                    return collectionMember == creator;
                }

                public object GetList()
                {
                    return list;
                }
            }
        }
    }
}
